#include "json.hpp"
#include <iostream>
#include <thread>
#include <string>
#include <chrono> // 获取系统的当前时间
#include <ctime>
using namespace std;
using json = nlohmann::json;

#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h> // 包含inet_addr和sockaddr_in定义
#include <semaphore.h> // for semaphore
#include <atomic>      // 原子变量反馈登录状态是否成功

#include "group.hpp"
#include "groupuser.hpp"
#include "user.hpp"
#include "public.hpp"

// 记录当前系统登录的用户信息
User g_currentUser;
// 记录当前登陆用户的好友列表信息
std::vector<User> g_currentUserFriendList;
// 记录当前登陆用户的群组列表信息
std::vector<Group> g_currentUserGroupList;

// 控制主菜单页面程序的全局变量
bool g_isMainMenuRunning = false;

// 用于读写线程之间的通信
sem_t rwsem; // sem_init(&rwsem, 0, 0) sem_destroy(&rwsem) sem_post(&rwsem) sem_wait(&rwsem)
// 记录登录状态是否成功 因为在多个线程中处理， 所以采用原子变量
std::atomic<bool> g_isLoginSuccess{false}; // g_isLoginSuccess.store(true) g_isLoginSuccess.load()

// 接收线程
void readTaskHandler(int clientfd);
// 获取系统时间 (聊天信息需要添加时间显示)
std::string getCurrentTime();
// 主聊天页面程序
void mainMenu(int clientfd);
// 显示当前登陆成功用户的基本信息
void showCurrentUserData();

// 设置服务器信息
void setupServerInfo(sockaddr_in &server, const char *ip, uint16_t port);

// 处理登录响应的业务逻辑
void doLoginResponse(nlohmann::json &responsejs);
// 处理注册响应的业务逻辑
void doRegResponse(nlohmann::json &responsejs);

// 聊天客户端程序实现， 主线程用作发送线程， 子线程用作接收线程
int main(int argc, char **argv)
{
    if (argc < 3)
    {
        std::cerr << "Command ivalid! example: ./ChatClient 127.0.0.1 6000" << std::endl;
        exit(-1);
    }

    // 解析通过命令行参数传递的IP 和 PORT
    char *ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    // 1.创建客户端的socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == clientfd)
    {
        std::cerr << "Socket create error" << endl;
        exit(-1);
    }

    // 填写client需要连接的服务器信息
    sockaddr_in server;
    setupServerInfo(server, ip, port);

    // 2.client 和 server 进行连接
    if (-1 == connect(clientfd, (sockaddr *)&server, sizeof(sockaddr_in)))
    {
        std::cerr << "Connect server error" << std::endl;
        close(clientfd); // 连接失败释放资源
        exit(-1);
    }

    // 3.初始化读写线程通信用到的semaphore, 参数二：0表示信号用于线程间通信
    if (sem_init(&rwsem, 0, 0) != 0)
    {
        std::cerr << "Failed to initialize semaphore " << std::endl;
        exit(-1);
    }

    // 3.连接成功就开启子线程：用于接收
    std::thread readTask(readTaskHandler, clientfd); // pthread_create
    readTask.detach();                               // pthread_detach

    // 4.随后循环主线程：负责发送数据
    for (;;)
    {
        // 显示首页面菜单 登陆 注册 退出
        std::cout << "===========================" << std::endl;
        std::cout << "1. login" << std::endl;
        std::cout << "2. register" << std::endl;
        std::cout << "3. quit" << std::endl;
        std::cout << "===========================" << std::endl;
        std::cout << "choice:";
        int choice = 0;
        cin >> choice;
        cin.get(); // 读取缓冲区残留的回车

        switch (choice)
        {
        case 1: // login业务
        {
            int id = 0;
            char pwd[50] = {0};
            std::cout << "userid:";
            cin >> id;
            cin.get(); // 读掉缓冲区残留的回车
            std::cout << "userpassword:";
            cin.getline(pwd, 50);

            nlohmann::json js;
            js["msgid"] = LOGIN_MSG;
            js["id"] = id;
            js["password"] = pwd;
            std::string request = js.dump();

            // 每次登录前先设置成false
            g_isLoginSuccess = false;
            int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0);
            if (-1 == len)
            {
                std::cerr << "send login msg error!" << request << std::endl;
            }

            // 等待子线程recv()完成后
            sem_wait(&rwsem); // 等待信号量， 由子线程处理完登录的responsejs后， 通知这里
            if (g_isLoginSuccess == true)
            {
                // 2.如果登录成功则主线程进入主菜单
                g_isMainMenuRunning = true;
                mainMenu(clientfd);
            }
        }
        break;
        case 2: // register业务
        {
            char name[50] = {0};
            char pwd[50] = {0};
            std::cout << "username:";
            cin.getline(name, 50);
            std::cout << "userpassword:";
            cin.getline(pwd, 50);

            nlohmann::json js;
            js["msgid"] = REG_MSG;
            js["name"] = name;
            js["password"] = pwd;
            std::string request = js.dump();

            int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0);
            if (-1 == len)
            {
                std::cerr << "send reg msg error:" << request << std::endl;
            }
            sem_wait(&rwsem); // 等待信号量， 子线程处理完注册消息会通知
        }
        break;
        case 3: // quit业务
            close(clientfd);
            sem_destroy(&rwsem); // 清理信号量
            exit(0);
        default:
            std::cerr << "Invalid input!" << std::endl;
            break;
        } // end of swich
    } // end of for(;;)
    return 0;
}

// 显示当前登陆成功用户的基本信息
void showCurrentUserData()
{
    std::cout << "========login user =======" << std::endl;
    std::cout << "current login user => id:" << g_currentUser.getId()
              << "name:" << g_currentUser.getName() << std::endl;
    // 1.打印好友列表
    std::cout << "-----------friend list-----------" << std::endl;
    if (!g_currentUserFriendList.empty())
    {
        for (const User &user : g_currentUserFriendList)
        {
            std::cout << user.getId() << " "
                      << user.getName() << " "
                      << user.getState() << std::endl;
        }
    }
    // 2.打印群组列表
    std::cout << "-----------group list-----------" << std::endl;
    if (!g_currentUserGroupList.empty())
    {
        // 2.1遍历当前用户所在的所有群组
        for (Group &group : g_currentUserGroupList)
        {
            std::cout << group.getGId() << " "
                      << group.getName() << " "
                      << group.getDesc() << std::endl;
            // 2.2获取当前群组的所有用户
            for (GroupUser &user : group.getUser())
            {
                std::cout << user.getId() << " "
                          << user.getName() << " "
                          << user.getState() << " "
                          << user.getRole() << std::endl;
            }
        }
    }
    std::cout << "==========================" << std::endl;
}

// "help" command handler
void help(int fd = 0, std::string str = "");
// "chat" command handler
void chat(int, std::string);
// "addfriend" command handler
void addfriend(int, std::string);
// "creategroup" command handler
void creategroup(int, std::string);
// "addgroup" command handler
void addgroup(int, std::string);
// "groupchat" command handler
void groupchat(int, std::string);
// "loginout" command handler
void loginout(int, std::string);

// 系统支持的客户端命令列表， 使用无序键值映射表
std::unordered_map<std::string, std::string> commandMap = {
    {"help", "显示所有支持的命令， 格式help"},
    {"chat", "一对一聊天， 格式chat:friendid:message"},
    {"addfriend", "添加好友， 格式addfriend:friendid"},
    {"creategroup", "创建群组， 格式creategroup:groupname:groupdesc"},
    {"addgroup", "加入群组， 格式addgroup:groupid"},
    {"groupchat", "群聊， 格式groupchat:groupid:message"},
    {"loginout", "注销， 格式loginout"}};

// 注册系统支持的客户端命令处理
using CommandHandler = std::function<void(int, std::string)>;
using CommandHandlerMap = std::unordered_map<std::string, CommandHandler>;
CommandHandlerMap commandHandlerMap = {
    {"help", help},
    {"chat", chat},
    {"addfriend", addfriend},
    {"creategroup", creategroup},
    {"addgroup", addgroup},
    {"groupchat", groupchat},
    {"loginout", loginout}};

// 接收线程: 接收数据 ，显示数据
void readTaskHandler(int clientfd)
{
    for (;;)
    {
        char buffer[1024] = {0};
        int len = recv(clientfd, buffer, sizeof(buffer), 0);
        if (-1 == len || 0 == len)
        {
            close(clientfd);
            exit(-1);
        }
        // 接收ChatServer转发的数据
        // 反序列化：json字符串恢复成json数据对象
        nlohmann::json js = nlohmann::json::parse(buffer);
        int msgtype = js["msgid"].get<int>();
        switch (msgtype)
        {
        case LOGIN_MSG_ACK: // 来自服务器的用户登录反馈
        {
            doLoginResponse(js); // 处理登录反馈的业务逻辑
            sem_post(&rwsem);    // 释放信号, 通知主线程， 登录结果处理完成
        }
        break;
        case REG_MSG_ACK: // 来自服务器的用户注册反馈
        {
            doRegResponse(js);
            sem_post(&rwsem);
        }
        break;
        case ONE_CHAT_MSG: // 单聊业务
        {
            std::cout << js["time"].get<string>() << "["
                      << js["id"].get<int>() << "]"
                      << js["name"].get<string>() << " said: "
                      << js["msg"].get<string>() << std::endl;
        }
        break;
        case GROUP_CHAT_MSG: // 群聊业务
        {
            std::cout << "群消息[" << js["groupid"] << "]"
                      << js["time"].get<string>() << "["
                      << js["id"].get<int>() << "]"
                      << js["name"].get<string>() << " said: "
                      << js["msg"].get<string>() << std::endl;
        }
        break;
        default:
            break;
        }
    }
}

// 获取系统时间 (聊天信息需要添加时间显示)
std::string getCurrentTime()
{
    // 获取当前时间点
    auto now = std::chrono::system_clock::now();
    // 转换为time_t类型表示日历时间，方便格式化输出
    std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
    // 格式化人类可读的时间信息输出
    std::tm buf;
    localtime_r(&now_time_t, &buf);

    // std::ostringstream oss;
    // oss << std::put_time(&buf, "%Y-%m-%d %H:%M:%S");
    // return oss.str();
    char buffer[80];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &buf);

    return std::string(buffer);
}

// 主聊天页面程序
void mainMenu(int clientfd)
{
    help(); // 显示系统所支持的命令

    // 1声明一个大小为1024的字符数组
    char buffer[1024] = {0};
    while (g_isMainMenuRunning)
    {
        // 2.存储用户输入的字符串，包括空格，直到按下回车键为止
        cin.getline(buffer, 1024);
        std::string commandbuf(buffer);
        std::string command;            // 存储命令
        int idx = commandbuf.find(":"); // 查找":"字符首次出现位置
        if (std::string::npos == idx)
        {
            // 没有找到":", 则整个commandbuf都被视为命令
            command = commandbuf;
        }
        else
        { // 找到：[0, idx]就是冒号前的部份是实际需要执行的命令
            command = commandbuf.substr(0, idx);
        }
        // 根据执行的命令作为KEY
        auto it = commandHandlerMap.find(command);
        // 没有找到乱写的命令
        if (it == commandHandlerMap.end())
        {
            std::cerr << "Invalid input command!" << std::endl;
            continue;
        }
        // 调用相应command的事件处理回调
        if (idx != std::string::npos)
        {
            // 只有当找到了 ":" 后才尝试提取后面的参数  0123:56789  [4 +1,]
            it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - (idx + 1)));
        }
        else
        {
            // 如果没有 ":"，则可能不需要传递额外参数，或者按需处理
            it->second(clientfd, " ");
        }
    }
}

// 设置服务器信息
void setupServerInfo(sockaddr_in &server, const char *ip, uint16_t port)
{
    memset(&server, 0, sizeof(server));

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip);
    if (server.sin_addr.s_addr == INADDR_NONE)
    { // P地址是否有效
        std::cerr << "Invalid IP address" << std::endl;
        return;
    }
    server.sin_port = htons(port);
}

// "help" command handler
void help(int fd, std::string str)
{
    std::cout << "show command list >>> " << std::endl;
    for (auto &comm : commandMap)
    {
        std::cout << comm.first << " : " << comm.second << endl;
    }
    std::cout << std::endl;
}

// "chat" command handler   friendid:message
void chat(int clientfd, std::string str)
{
    int idx = str.find(":");
    if (std::string::npos == idx)
    {
        std::cerr << "chat command invalid!" << std::endl;
        return;
    }

    int friendid = std::stoi(str.substr(0, idx).c_str());
    // 得到xxxx\0
    std::string message = str.substr(idx + 1, str.size() - idx);

    nlohmann::json js;
    js["msgid"] = ONE_CHAT_MSG;
    js["id"] = g_currentUser.getId();
    js["name"] = g_currentUser.getName();
    js["receiverId"] = friendid;
    js["msg"] = message;
    js["time"] = getCurrentTime();
    std::string buffer = js.dump();

    int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
    if (-1 == len)
    {
        std::cerr << "send chat msg error -> " << buffer << std::endl;
    }
}

// "addfriend" command handler   addfriend:friendid
void addfriend(int clientfd, std::string str)
{
    int friendid = std::stoi(str.c_str());
    nlohmann::json js;
    js["msgid"] = ADD_FRIEND_MSG;
    js["id"] = g_currentUser.getId();
    js["friendid"] = friendid;
    std::string buffer = js.dump();

    int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
    if (-1 == len)
    {
        std::cerr << "send addfriend msg error -> " << buffer << std::endl;
    }
}

// "creategroup" command handler  groupname:groupdesc
void creategroup(int clientfd, std::string str)
{
    int idx = str.find(":");
    if (std::string::npos == idx)
    {
        std::cerr << "creategroup command invalid!" << std::endl;
        return;
    }

    std::string groupname = str.substr(0, idx);
    std::string groupdesc = str.substr(idx + 1, str.size() - (idx + 1));

    nlohmann::json js;
    js["msgid"] = CREATE_GROUP_MSG;
    js["id"] = g_currentUser.getId();
    js["groupname"] = groupname;
    js["groupdesc"] = groupdesc;
    std::string buffer = js.dump();
    int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
    if (-1 == len)
    {
        std::cerr << "send creategroup msg error -> " << buffer << std::endl;
    }
}

// "addgroup" command handler addgroup:groupid
void addgroup(int clientfd, std::string str)
{
    int groupid = std::stoi(str);

    nlohmann::json js;
    js["msgid"] = ADD_GROUP_MSG;
    js["id"] = g_currentUser.getId();
    js["groupid"] = groupid;
    std::string buffer = js.dump();

    int len = send(clientfd, buffer.c_str(), buffer.length(), 0);
    if (-1 == len)
    {
        std::cerr << "send addgroup msg error -> " << buffer << std::endl;
    }
}

// "groupchat" command handler   groupid:message
void groupchat(int clientfd, std::string str)
{
    int idx = str.find(":");
    if (std::string::npos == idx)
    {
        std::cerr << "groupchat command invalid!" << std::endl;
        return;
    }
    int groupid = std::stoi(str.substr(0, idx));
    std::string message = str.substr(idx + 1, str.size() - (idx + 1));
    nlohmann::json js;
    js["msgid"] = GROUP_CHAT_MSG;
    js["id"] = g_currentUser.getId();
    js["name"] = g_currentUser.getName();
    js["groupid"] = groupid;
    js["msg"] = message;
    js["time"] = getCurrentTime();
    std::string buffer = js.dump();
    int len = send(clientfd, buffer.c_str(), buffer.length(), 0);
    if (-1 == len)
    {
        std::cerr << "send groupchat msg error -> " << buffer << std::endl;
    }
}

// "loginout" command handler
void loginout(int clientfd, std::string str)
{
    nlohmann::json js;
    js["msgid"] = LOGINOUT_MSG;
    js["id"] = g_currentUser.getId();
    std::string buffer = js.dump();

    int len = send(clientfd, buffer.c_str(), buffer.length(), 0);
    if (-1 == len)
    {
        std::cerr << "send loginout msg error -> " << buffer << std::endl;
    }
    else
    {
        g_isMainMenuRunning = false;
    }
}

// 处理登录响应的业务逻辑
void doLoginResponse(nlohmann::json &responsejs)
{
    // 将接收到的string反序列化为json对象:responsejs
    if (0 != responsejs["errno"].get<int>())
    { // 登陆失败
        std::cerr << responsejs["errmsg"] << std::endl;
        g_isLoginSuccess = false;
    }
    else
    {
        // 登录成功
        // 记录当前用户的ID 和 NAME
        g_currentUser.setId(responsejs["id"].get<int>());
        g_currentUser.setName(responsejs["name"]);

        // 记录当前用户的好友列表信息
        if (responsejs.contains("friends"))
        {
            // 初始化
            g_currentUserFriendList.clear();

            std::vector<std::string> u_vec = responsejs["friends"];
            for (std::string &user_str : u_vec)
            {
                // 反序列化为js对象
                nlohmann::json js = nlohmann::json::parse(user_str);
                User user;
                user.setId(js["id"].get<int>());
                user.setName(js["name"]);
                user.setState(js["state"]);
                g_currentUserFriendList.push_back(user);
            }
        }

        // 记录当前用户的群组列表信息
        if (responsejs.contains("groups"))
        {
            // 初始化该用户的所属群容器
            g_currentUserGroupList.clear();

            std::vector<std::string> g_vec = responsejs["groups"];
            for (std::string &group_str : g_vec)
            {
                nlohmann::json groupjs = nlohmann::json::parse(group_str);
                Group group;
                group.setId(groupjs["id"].get<int>());
                group.setName(groupjs["groupname"]);
                group.setDesc(groupjs["groupdesc"]);

                std::vector<std::string> u_vec = groupjs["users"];
                for (std::string &userstr : u_vec)
                {
                    GroupUser groupuser;
                    nlohmann::json js = nlohmann::json::parse(userstr);
                    groupuser.setId(js["id"].get<int>());
                    groupuser.setName(js["name"]);
                    groupuser.setState(js["state"]);
                    groupuser.setRole(js["role"]);
                    group.getUser().push_back(groupuser);
                }
                g_currentUserGroupList.push_back(group);
            }
        } // end of if (responsejs.contains("groups"))
        // 登录成功 即responsejs["errno"].get<int>() == 0
        // 显示登陆用户的基本信息
        showCurrentUserData();

        // 显示当前用户的offlinemsg：个人聊天信息或群组消息
        if (responsejs.contains("offlinemsg"))
        {
            std::vector<std::string> vec = responsejs["offlinemsg"];
            for (std::string &str : vec)
            {
                nlohmann::json js = nlohmann::json::parse(str);
                // time + [id] + name + " said: " + xxx
                if (ONE_CHAT_MSG == js["msgid"].get<int>())
                { // 个人离线消息
                    std::cout << js["time"].get<string>() << "["
                              << js["id"].get<int>() << "]"
                              << js["name"].get<string>() << " said: "
                              << js["msg"].get<string>() << std::endl;
                }
                else
                { // 群离线消息
                    std::cout << "群消息[" << js["groupid"] << "]"
                              << js["time"].get<string>() << "["
                              << js["id"].get<int>() << "]"
                              << js["name"].get<string>() << " said: "
                              << js["msg"].get<string>() << std::endl;
                }
            }
        }
        // 登录成功在子线程完成相当操作之后，再给开关一个=true值
        g_isLoginSuccess = true;
    }
}

// 处理注册响应的业务逻辑
void doRegResponse(nlohmann::json &responsejs)
{
    if (0 != responsejs["errno"].get<int>())
    { // 注册失败
        std::cerr << "is already exists, register error!" << std::endl;
    }
    else
    { // 注册成功
        std::cout << " register sucess, userid is " << responsejs["id"]
                  << ", do not forget it !" << std::endl;
    }
}